<?php
namespace app\common\components;


use app\common\utils\facade\EncryptUtil;
use Closure;
use think\Exception;
use think\facade\Event;
use think\facade\Log;
use think\facade\Route;

class User
{
    const EVENT_BEFORE_LOGIN = 'beforeLogin';
    const EVENT_AFTER_LOGIN = 'afterLogin';
    const EVENT_BEFORE_LOGOUT = 'beforeLogout';
    const EVENT_AFTER_LOGOUT = 'afterLogout';

    //当前用户失实例
    protected static $identityClass;

    //登录url
    protected static $loginUrl;

    //加密盐值
    public $encryptSlat;

    //激活自动登录
    public $enableAutoLogin = false;
    //是否刷新自动登录凭证
    public $autoRenewCookie = true;

    //过期时间
    public $authTimeout;
    //绝对过期时间
    public $absoluteAuthTimeout;
    //自动登录cookie标识
    public $identityCookieParam ='_identity';

    //用户凭证标识
    public $idParam = '__id';
    //session 过期时间参数
    public $authTimeoutParam = '__expire';
    //session 绝对过期时间参数
    public $absoluteAuthTimeoutParam = '__absoluteExpire';

    //记录登录后要跳转的url
    public $returnUrlParam = '__returnUrl';


    //用户实例
    private $_identity = false;

    /**
     * @var Closure[]
     */
    protected static $maker = [];



    //检查参数是否完成
    public function __construct(){
        if (static::$identityClass === null) {
            throw new Exception('User::$identityClass must be set.');
        }
        if (static::$loginUrl === null) {
            throw new Exception('User::$loginUrl must be set.');
        }
        if ($this->encryptSlat === null) {
            $this->encryptSlat = self::class;
        }

        if (!empty(static::$maker)) {
            foreach (static::$maker as $maker) {
                call_user_func($maker, $this);
            }
        }
    }

    /**
     * 设置服务注入
     * @access public
     * @param Closure $maker
     * @return void
     */
    public static function maker(Closure $maker)
    {
        static::$maker[] = $maker;
    }

    /**
     * 设置user模型
     * @param string $class
     */
    public static function setIdentityClass(string $class){
        static::$identityClass  = $class;
    }



    /**
     * 登录操作
     * @param IdentityInterface $identity user模型
     * @param int $duration 免登陆时效
     * @return bool 登录成功返回true 否则false
     */
    public function login(IdentityInterface $identity, $duration = 0)
    {
        if ($this->beforeLogin($identity)) {

            //登录实现
            $this->switchIdentity($identity, $duration);
            $this->afterLogin($identity);
        }
        return !$this->isGuest();
    }

    //判断是否是游客
    public function isGuest(){
        return $this->getIdentity() === null;
    }

    //获取当前用户的id
    public function getId(){
        return $this->getIdentity()->id ?? null;
    }

    /**
     * 登出操作
     * @param bool $destroySession
     * @return bool
     * @throws \Throwable
     */
    public function Logout($destroySession = true){
        $identity = $this->getIdentity();
        if ($identity !== null && $this->beforeLogout($identity)) {
            $this->switchIdentity(null);

            //清空当前会话所有session
            if ($destroySession) {
                app()->session->clear();
            }
            $this->afterLogout($identity);
        }
        return $this->isGuest();
    }

    //设置用户实例
    public function setIdentity($identity){
        if ($identity instanceof IdentityInterface) {
            $this->_identity = $identity;
        } elseif ($identity === null) {
            $this->_identity = null;
        } else {
            throw new Exception('The identity object must implement IdentityInterface.');
        }
    }

    //刷新用户实例
    protected function renewAuthStatus(){
        //获取session
        $session = app()->session;
        //获取session中的用户id
        $id = $session->get($this->idParam);

        if ($id === null) {
            $identity = null;
        } else {
            $class = static::$identityClass;
            $identity = $class::findIdentity($id);
        }
        //设置到属性  __identity
        $this->setIdentity($identity);

        if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {
            $expire = $this->authTimeout !== null ? $session->get($this->authTimeoutParam) : null;
            $expireAbsolute = $this->absoluteAuthTimeout !== null ? $session->get($this->absoluteAuthTimeoutParam) : null;
            if ( ($expire !== null && $expire < time()) || ($expireAbsolute !== null && $expireAbsolute < time()) ) {

                //超时直接登出
                $this->logout(false);

            } elseif ($this->authTimeout !== null) {
                //刷新超时时间
                $session->set($this->authTimeoutParam, time() + $this->authTimeout);
            }
        }

        //自动登录
        if ($this->enableAutoLogin) {
            if ($this->isGuest()) {/**/
                $this->loginByCookie();
            } elseif ($this->autoRenewCookie) {
                $this->renewIdentityCookie();
            }
        }
    }

    /*刷新cookie*/
    protected function renewIdentityCookie(){
        $cookie = app()->cookie;
        $name = $this->identityCookieParam;
        $value = $cookie->get($name);
        if ($value !== null) {
            $data = $this->deserialize($value);
            if (is_array($data) && count($data) == 3) {
                $expire = (int) $data[2];
                $cookie->set($this->identityCookieParam, $value, $expire);
            }
        }
    }

    //获取自动登录凭证
    protected function getIdentityAndDurationFromCookie(){
        $cookie = app()->cookie;
        $value = $cookie->get($this->identityCookieParam);
        if ($value === null) {
            return null;
        }
        $data = $this->deserialize($value);

        if (is_array($data) && count($data) == 3) {
            list($id, $authKey, $duration) = $data;

            $class = static::$identityClass;
            $identity = $class::findIdentity($id);
            if ($identity !== null) {
                if (!$identity instanceof IdentityInterface) {
                    throw new Exception("$class::findIdentity() must return an object implementing IdentityInterface.");
                } elseif (!$identity->validateAuthKey($authKey)) {
                    Log::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);
                } else {
                    return ['identity' => $identity, 'duration' => $duration];
                }
            }
        }
        $this->removeIdentityCookie();
        return null;
    }

    //cookie 登录
    protected function loginByCookie(){
        $data = $this->getIdentityAndDurationFromCookie();
        if (isset($data['identity'], $data['duration'])) {
            $identity = $data['identity'];
            $duration = $data['duration'];
            if ($this->beforeLogin($identity)) {
                $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);
                $this->afterLogin($identity);
            }
        }
    }

    //获取用户实例
    public function getIdentity($autoRenew = true)
    {
        if ($this->_identity === false) {
            if ($autoRenew) {
                try {
                    $this->_identity = null;
                    $this->renewAuthStatus();
                } catch (\Exception $e) {
                    $this->_identity = false;
                    throw $e;
                } catch (\Throwable $e) {
                    $this->_identity = false;
                    throw $e;
                }
            } else {
                return null;
            }
        }

        return $this->_identity;
    }

    //删除自动登录凭证
    protected function removeIdentityCookie(){
        app()->cookie->delete($this->identityCookieParam);
    }

    //序列化并加密
    private function serialize(array $arr){
        $value = json_encode($arr, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        return EncryptUtil::encryption($value, 'E', $this->encryptSlat);
    }
    //反序列化解密
    private function deserialize(string $str){
        $json = EncryptUtil::encryption($str, 'D', $this->encryptSlat);
        return json_decode($json,true);
    }

    //添加自动登录凭证
    protected function sendIdentityCookie(IdentityInterface $identity, $duration){
        $value = $this->serialize([
            $identity->getId(),
            $identity->getAuthKey(),
            $duration
        ]);
        app()->cookie->set($this->identityCookieParam, $value, $duration);
    }

    /**
     * 切换用户
     * @param $identity
     * @param int $duration
     * @throws Exception
     */
    public function switchIdentity($identity, $duration = 0){
        $this->setIdentity($identity);

        //激活了自动登录删除cookie自动登录数据
        if ($this->enableAutoLogin && ($this->autoRenewCookie || $identity === null)) {
            $this->removeIdentityCookie();
        }

        //重置session信息
        $session = app()->session;
        if ($session->has($this->idParam)){
            $session->delete($this->idParam);
            $session->delete($this->authTimeoutParam);
            $session->delete($this->absoluteAuthTimeoutParam);
        }
        //如果是切换用户
        if ($identity){
            $session->set($this->idParam, $identity->getId());
            if ($this->authTimeout !== null) {
                $session->set($this->authTimeoutParam, time() + $this->authTimeout);
            }
            if ($this->absoluteAuthTimeout !== null) {
                $session->set($this->absoluteAuthTimeoutParam, time() + $this->absoluteAuthTimeout);
            }
            //激活了自动登录 就设置cookie登录凭证
            if ($this->enableAutoLogin && $duration > 0) {
                $this->sendIdentityCookie($identity, $duration);
            }
        }

    }

    /*登录之前触发*/
    protected function beforeLogin($identity){
        if ($identity->beforeLogin()){
            Event::trigger(self::EVENT_BEFORE_LOGIN,$identity);
            return true;
        }
        return false;

    }
    /*登录之后触发*/
    protected function afterLogin($identity){
        $identity->afterLogin();
        Event::trigger(self::EVENT_AFTER_LOGIN,$identity);
    }
    /*登出之前触发*/
    protected function beforeLogout($identity){
        if ($identity->beforeLogout()){
            Event::trigger(self::EVENT_BEFORE_LOGOUT,$identity);
            return true;
        }
        return false;

    }
    /*登出之后触发*/
    protected function afterLogout($identity){
        $identity->afterLogout();
        Event::trigger(self::EVENT_AFTER_LOGOUT,$identity);
    }

    /**
     * 获取返回的url
     * @return string \think\route\Url
     */
    public function getReturnUrl(){
        $session = app()->session;
        $url = $session->get($this->returnUrlParam, '') ?: (string) url(request()->rootPathInfo());
        $session->delete($this->returnUrlParam);
        return $url;
    }

    /**
     * 设置返回的url
     * @param string $url
     * @throws Exception
     */
    public function setReturnUrl(string $url){
        if (!isset($url)) throw new Exception('route param error');
        app()->session->set($this->returnUrlParam, $url);
    }

    /**
     * 获取登录url
     * @return mixed
     */
    public function getLoginUrl(){
        return static::$loginUrl;
    }

    /**
     * 设置登录url
     * @param string $url
     * @throws Exception
     */
    public static function setLoginUrl(string $url){
        if (empty($url)) throw new Exception('route param error');
        static::$loginUrl = $url;
    }

    /**
     * 记住当前url并跳转到登录页
     * @return bool|\think\response\Json|\think\response\Redirect|null
     * @throws Exception
     * @throws \Throwable
     */
    public function loginRequired()
    {
        //不是游客 直接返回
        if(!$this->isGuest()) return $this->getIdentity();

        if (static::$loginUrl) {
            //是游客就记住当前url
            $request = request();

            if (!$request->isAjax()) {
                $this->setReturnUrl(request()->url() ?? '');
                //跳至登录页
                return redirect(static::$loginUrl);
            }else{
                $this->setReturnUrl(request()->header('referer') ?? '');
                //跳至登录页
                return json(['code'=>1,'msg'=>'请先登录.','url'=>static::$loginUrl]);
            }
        }

        //Response::create($this->loginUrl, 'redirect', 302)->send();
        throw new Exception('Login Required');
    }


}